﻿#if !NET20

using Apewer;
using System;
using System.Collections.Generic;
using System.Net;

namespace Apewer.WebSocket
{

    /// <summary></summary>
    public sealed class GenericServer : IDisposable
    {

        private Dictionary<int, Connection> _connections = new Dictionary<int, Connection>();
        private WebSocketServer _server = null;

        private bool _running = false;
        private IPAddress _address = null;
        private int _port = 0;

        /// <summary></summary>
        public event SocketEvent OnOpen;

        /// <summary></summary>
        public event SocketEvent OnClose;

        /// <summary></summary>
        public event SocketEvent<string> OnMessage;

        /// <summary></summary>
        public event SocketEvent<byte[]> OnBytes;

        /// <summary></summary>
        public event SocketEvent<byte[]> OnPing;

        /// <summary></summary>
        public event SocketEvent<byte[]> OnPong;

        /// <summary></summary>
        public event SocketEvent<Exception> OnError;

        /// <summary></summary>
        public event ServerEvent<Exception> Excepted;

        #region Properties

        /// <summary>正在监听。</summary>
        public bool Running
        {
            get { return _running; }
        }

        /// <summary>监听的地址。</summary>
        public IPAddress Address
        {
            get { return _address; }
        }

        /// <summary>监听的端口号。</summary>
        public int Port
        {
            get { return _port; }
        }

        /// <summary>当前 Sockets 数量。</summary>
        public int Count
        {
            get
            {
                var count = 0;
                lock (_connections) { count = _connections.Count; }
                return count;
            }
        }

        #endregion

        #region Methods

        /// <summary></summary>
        public GenericServer() { }

        /// <summary>关闭服务，并释放系统资源。</summary>
        public void Dispose()
        {
            lock (_connections)
            {
                _connections.Clear();
            }
            lock (_server)
            {
                if (_server != null)
                {
                    _server.Dispose();
                }
            }
            _server = null;
            _running = false;
        }

        /// <summary>启动监听，如果监听正在运行则失败。在所有 IPv4 网络接口上自动选择可用的端口号。</summary>
        /// <returns>已监听的端口号。</returns>
        /// <exception cref="FormatException" />
        /// <exception cref="InvalidOperationException" />
        public void Start() => Start(0, null);

        /// <summary>启动监听，如果监听正在运行则失败。</summary>
        /// <param name="endPoint">要监听的终结点。指定为 NULL 时将在所有 IPv4 网络接口上自动选择可用的端口号。</param>
        /// <returns>已监听的端口号。</returns>
        /// <exception cref="ArgumentOutOfRangeException" />
        /// <exception cref="FormatException" />
        /// <exception cref="InvalidOperationException" />
        public void Start(IPEndPoint endPoint)
        {
            if (endPoint == null) Start(0, null);
            else Start(endPoint.Port, endPoint.Address);
        }

        /// <summary>启动监听，如果监听正在运行则失败。</summary>
        /// <param name="port">要监听的端口号。指定为 0 时将自动选择可用的端口号。</param>
        /// <param name="address">要监听的网络接口。指定为 NULL 时将在所有 IPv4 网络接口监听，等同于 <see cref="IPAddress.Any"/>。</param>
        /// <returns>已监听的端口号。</returns>
        /// <exception cref="ArgumentOutOfRangeException" />
        /// <exception cref="FormatException" />
        /// <exception cref="InvalidOperationException" />
        public void Start(int port, IPAddress address = null)
        {
            if (address == null) address = IPAddress.Any;
            if (port < 0) throw new ArgumentOutOfRangeException(nameof(port));
            if (port > 65535) throw new ArgumentOutOfRangeException(nameof(port));
            if (_running) throw new InvalidOperationException("示例已经在运行中，无法再次启动。");

            _address = address;
            _port = port;

            var location = "ws://" + _address.ToString() + ":" + port.ToString();
            _server = new WebSocketServer(location);
            _server.Start(Initialize);
            _port = _server.Port;
            _running = true;
        }

        /// <summary>对所有连接发送文本。</summary>
        /// <exception cref="InvalidOperationException"></exception>
        public int Send(params char[] message)
        {
            var text = null as string;
            if (message == null || message.Length < 1) return 0;
            if (message.Length == 1)
            {
                text = message[0].ToString();
            }
            else
            {
                var sb = new System.Text.StringBuilder();
                foreach (var i in message)
                {
                    if ((object)i != null) sb.Append(i);
                }
                text = sb.ToString();
            }
            if (text.Length < 1) return 0;

            var connections = GetConnections();
            var count = 0;
            foreach (var connection in connections)
            {
                try
                {
                    var sent = connection.Send(message);
                    if (sent != null) count += 1;
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            }
            return count;
        }

        /// <summary>发送文本。</summary>
        /// <exception cref="InvalidOperationException"></exception>
        public int Send(params string[] message)
        {
            var text = null as string;
            if (message == null || message.Length < 1) return 0;
            if (message.Length == 1)
            {
                text = message[0].ToString();
            }
            else
            {
                var sb = new System.Text.StringBuilder();
                foreach (var i in message)
                {
                    if (i != null) sb.Append(i);
                }
                text = sb.ToString();
            }
            if (text.Length < 1) return 0;

            var connections = GetConnections();
            var count = 0;
            foreach (var connection in connections)
            {
                try
                {
                    var sent = connection.Send(message);
                    if (sent != null) count += 1;
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            }
            return count;
        }

        /// <summary>发送字节数组。</summary>
        /// <exception cref="InvalidOperationException"></exception>
        public int Send(params byte[] message)
        {
            var connections = GetConnections();
            var count = 0;
            foreach (var connection in connections)
            {
                try
                {
                    var sent = connection.Send(message);
                    if (sent != null) count += 1;
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            }
            return count;
        }

        /// <summary>发送 PING。</summary>
        /// <exception cref="InvalidOperationException"></exception>
        public int Ping(params byte[] message)
        {
            var connections = GetConnections();
            var count = 0;
            foreach (var connection in connections)
            {
                try
                {
                    var sent = connection.Ping(message);
                    if (sent != null) count += 1;
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            }
            return count;
        }

        /// <summary>发送 PONG。</summary>
        /// <exception cref="InvalidOperationException"></exception>
        public int Pong(params byte[] message)
        {
            var connections = GetConnections();
            var count = 0;
            foreach (var connection in connections)
            {
                try
                {
                    var sent = connection.Pong(message);
                    if (sent != null) count += 1;
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            }
            return count;
        }

        /// <summary>关闭 Socket 连接。</summary>
        public void Close()
        {
            lock (_server)
            {
                try
                {
                    if (_server != null && _server.ListenerSocket != null)
                    {
                        _server.ListenerSocket.Close();
                    }
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            }
        }

        #endregion

        #region Private

        List<Connection> GetConnections()
        {
            var list = new List<Connection>();
            lock (_connections)
            {
                foreach (var i in _connections.Values)
                {
                    if (i != null) list.Add(i);
                }
            }
            return list;
        }

        void RaiseExcepted(Exception exception)
        {
            if (exception == null) return;
            if (Excepted == null) return;
            try
            {
                InBackground(() => Excepted(this, exception));
            }
            catch { }
        }

        void Initialize(Connection socket)
        {
            if (socket == null) return;

            socket.OnOpen = () => InBackground(() =>
            {
                var hashcode = socket.GetHashCode();
                lock (_connections)
                {
                    if (!_connections.ContainsKey(hashcode))
                    {
                        _connections.Add(hashcode, socket);
                    }
                }
                try
                {
                    OnOpen?.Invoke(socket);
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            });

            socket.OnClose = () => InBackground(() =>
            {
                var hashcode = socket.GetHashCode();
                try
                {
                    OnClose?.Invoke(socket);
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            });

            socket.OnBytes = (content) => InBackground(() =>
            {
                try
                {
                    OnBytes?.Invoke(socket, content);
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            });

            socket.OnError = (content) => InBackground(() =>
            {
                try
                {
                    OnError?.Invoke(socket, content);
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            });

            socket.OnMessage = (content) => InBackground(() =>
            {
                try
                {
                    OnMessage?.Invoke(socket, content);
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            });

            socket.OnPing = (content) => InBackground(() =>
            {
                try
                {
                    if (OnPing == null) socket.Pong(content);
                    else OnPing?.Invoke(socket, content);
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            });

            socket.OnPong = (content) => InBackground(() =>
            {
                try
                {
                    OnPong?.Invoke(socket, content);
                }
                catch (Exception exception)
                {
                    RaiseExcepted(exception);
                }
            });
        }

        int GetHashCode(Connection connection)
        {
            if (connection == null) return 0;
            if (connection.ConnectionInfo == null) return 0;
            return connection.ConnectionInfo.Id.GetHashCode();
        }

        #endregion

        #region static

        /// <summary></summary>
        public static LogLevel LogLevel
        {
            get { return WebSocketLog.Level; }
            set { WebSocketLog.Level = value; }
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        private static void InBackground(System.Action action)
        {
            if (action == null) return;
            var thread = new System.Threading.Thread(delegate (object v) { ((System.Action)v)(); });
            thread.IsBackground = true;
            thread.Start(action);
        }

        #endregion

    }

}

#endif
